#include "types.h"
#include "globals.h"
#include "errno.h"

#include "util/debug.h"
#include "util/string.h"

#include "proc/proc.h"
#include "proc/kthread.h"

#include "mm/mm.h"
#include "mm/mman.h"
#include "mm/page.h"
#include "mm/pframe.h"
#include "mm/mmobj.h"
#include "mm/pagetable.h"
#include "mm/tlb.h"
#include "mm/slab.h"

#include "fs/file.h"
#include "fs/vnode.h"

#include "vm/shadow.h"
#include "vm/vmmap.h"

#include "api/exec.h"

#include "main/interrupt.h"

extern int TestMode;
extern void* fork_hello_world(int arg1, void *arg2);

/* Pushes the appropriate things onto the kernel stack of a newly forked thread
 * so that it can begin execution in userland_entry.
 * regs: registers the new thread should have on execution
 * kstack: location of the new thread's kernel stack
 * Returns the new stack pointer on success. */
static uint32_t
fork_setup_stack(const regs_t *regs, void *kstack)
{
        /* Pointer argument and dummy return address, and userland dummy return
         * address */
        uint32_t esp = ((uint32_t) kstack) + DEFAULT_STACK_SIZE - (sizeof(regs_t) + 12);
        *(void **)(esp + 4) = (void *)(esp + 8); /* Set the argument to point to location of struct on stack */
        memcpy((void *)(esp + 8), regs, sizeof(regs_t)); /* Copy over struct */
        return esp;
}


/*
 * The implementation of fork(2). Once this works,
 * you're practically home free. This is what the
 * entirety of Weenix has been leading up to.
 * Go forth and conquer.
 */
int
do_fork(struct regs *regs)
{
	dbg(DBG_PROC,"Return back,Now is the process %d\n",curproc->p_pid);
	KASSERT(regs != NULL);
    dbg(DBG_VM,"(GRADING 5.a) regs is not NULL\n");

    KASSERT(curproc != NULL);
    dbg(DBG_VM,"(GRADING 5.a) curproc is not NULL\n");

    KASSERT(curproc->p_state == PROC_RUNNING);
    dbg(DBG_VM,"(GRADING 5.a) curproc->p_state is not running\n");
	int i=0;
	int childret=0;
	pid_t pid;
	proc_t *forkproc;
	kthread_t *forkthread;
	forkproc=proc_create(curproc->p_comm);
	pid=forkproc->p_pid;
	forkthread=kthread_clone(curthr);
	/*-----------------------forkthread setting----------------*/
	
	regs->r_eax=0;
	/*context_setup(&(forkthread->kt_ctx),userland_entry, regs, NULL,forkthread->kt_kstack, DEFAULT_STACK_SIZE, curthr->kt_proc->p_pagedir);*/
	forkthread->kt_ctx.c_eip=TestMode?(uintptr_t) fork_hello_world:(uintptr_t) userland_entry;
	forkthread->kt_ctx.c_esp=TestMode?curthr->kt_ctx.c_esp:fork_setup_stack(regs, forkthread->kt_kstack);
	
	forkthread->kt_proc=forkproc;
	regs->r_eax=pid;
	/*forkthread->kt_retval=0;
	curthr->kt_retval=&pid;*/
	
	
	
	/*-----------------------forkproc setting-------------------*/
	list_insert_tail(&forkproc->p_threads, &forkthread->kt_plink);	
	
	forkproc->p_pproc=curproc;
	forkproc->p_pagedir = pt_create_pagedir();
	forkproc->p_status = curproc->p_status; 
        forkproc->p_state =  curproc->p_state;        
        /*forkproc->p_wait =  curproc->p_wait;*/     
	/*forkproc->p_pagedir = curproc->p_pagedir;*/
	forkthread->kt_ctx.c_pdptr=forkproc->p_pagedir;
	forkproc->p_brk=curproc->p_brk;
	forkproc->p_start_brk=curproc->p_start_brk;
	forkproc->p_cwd=curproc->p_cwd;
	vref(forkproc->p_cwd);
	for(i=0;i<NFILES;i++)
	{
		forkproc->p_files[i] = curproc->p_files[i];
		if(forkproc->p_files[i]!=NULL)
			fref(forkproc->p_files[i]);
	}

	KASSERT(forkproc->p_state == PROC_RUNNING);
    dbg(DBG_VM,"(GRADING 5.a) newproc->p_state is PROC_RUNNING\n");

    KASSERT(forkproc->p_pagedir != NULL);
    dbg(DBG_VM,"(GRADING 5.a) forkproc->p_pagedir is not NULL\n");

    KASSERT(forkthread->kt_kstack != NULL);
    dbg(DBG_VM,"(GRADING 5.a) forkthread->kt_kstack is not NULL\n");

	
	/*----------------------vmarea setting---------------------*/
	vmarea_t *forkvma,*curvma;
	list_t *temp;
	
	forkproc->p_vmmap = vmmap_clone(curproc->p_vmmap);
	temp=&forkproc->p_vmmap->vmm_list;
	list_iterate_begin(&curproc->p_vmmap->vmm_list,curvma, vmarea_t, vma_plink)
	{
		
		forkvma=list_head(temp, vmarea_t, vma_plink); 
		if(forkvma->vma_flags & MAP_PRIVATE)
		{
			mmobj_t *forkshadow,*curnewshadow;
			forkshadow=shadow_create();
			curnewshadow=shadow_create();
			forkshadow->mmo_shadowed = curvma->vma_obj;
			curnewshadow->mmo_shadowed=curvma->vma_obj;
			forkshadow->mmo_un.mmo_bottom_obj=curvma->vma_obj->mmo_un.mmo_bottom_obj;
			curnewshadow->mmo_un.mmo_bottom_obj=curvma->vma_obj->mmo_un.mmo_bottom_obj;
			forkvma->vma_obj=forkshadow;
			/*forkvma->vma_obj->mmo_ops->ref(forkvma->vma_obj);*/
			curvma->vma_obj=curnewshadow;
			/*curvma->vma_obj->mmo_ops->ref(curvma->vma_obj);*/		
		}
		else
		{
			forkvma->vma_obj= curvma->vma_obj;
			list_insert_tail(&(forkvma->vma_obj->mmo_un.mmo_vmas),&(forkvma->vma_olink));
			forkvma->vma_obj->mmo_ops->ref(forkvma->vma_obj);
		}
	}list_iterate_end();
	/*unmap tlb_flash*/	
		
	pt_unmap_range(curproc->p_pagedir,USER_MEM_LOW, USER_MEM_HIGH);
	tlb_flush_all();
	
	if(!TestMode)
		sched_make_runnable(forkthread);
	
	
	
	return pid;
	
	
        /*NOT_YET_IMPLEMENTED("VM: do_fork");
        return 0;*/
}
